package models;

import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;

import play.data.validation.Constraints.Pattern;
import play.data.validation.Constraints.Required;
import play.db.ebean.Model;
import helpers.*;

/**
 * Meta information for test case, test suite and test projects
 * 
 * @author june
 * 
 */
@Entity
@Table(name = "resources")
public class Resource extends Model {

	private static final long serialVersionUID = 1L;

	@Column(name = "id")
	@GeneratedValue(strategy = GenerationType.TABLE, generator = "SEQ_TABLE")
	@Id
	@TableGenerator(name = "SEQ_TABLE")
	private long id;

	@Column(name = "type")
	private ResourceType type;

	@Column(name = "company", nullable = false)
	@ManyToOne(fetch = FetchType.LAZY)
	private Company company;

	@Column(name = "project")
	@ManyToOne(fetch = FetchType.LAZY)
	private Project project;

	@Column(name = "name")
	@Pattern(value = "[A-Za-z0-9\\-_][A-Za-z0-9\\-_ ]+", message = "The name contains invalid characters. Valid characters are: A-Z, 0-9, hyphen (-), underscore (_) and white space ( ).")
	@Required
	private String name;

	@Column(name = "description")
	private String description;

	@Column(name = "resource_path")
	private String resourcePath;

	@JoinColumn(name = "parent_resource")
	@ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
	private Resource parentResource;

	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parentResource")
	private List<Resource> resources;

	@Column(name = "created")
	private Date created;

	@Column(name = "last_modified")
	private Date lastModified;

	public static Finder<Long, Resource> find = new Finder<Long, Resource>(long.class, Resource.class);

	/**
	 * Creates a new resource instance without company or project, aka. a global resource.
	 * 
	 * @param name
	 *            The resource name
	 */
	public Resource(ResourceType type, String name) {
		this.type = type;
		this.name = name;
	}

	/**
	 * Creates a new resource instance for a specific companay, aka. a common resource.
	 * 
	 * @param company
	 *            The company
	 * @param name
	 *            The name
	 */
	public Resource(ResourceType type, Company company, String name) {
		this.type = type;
		this.company = company;
		this.name = name;
	}

	/**
	 * Creates a new resource instance for specific company and project.
	 * 
	 * @param project
	 *            The project
	 * @param name
	 *            The resource name
	 */
	public Resource(ResourceType type, Project project, String name) {
		this.type = type;
		if (project != null) {
			this.company = project.getCompany();
		}
		this.project = project;
		this.name = name;
	}

	/**
	 * Returns the id of this resource
	 * 
	 * @return The id
	 */
	public long getId() {
		return id;
	}

	/**
	 * Returns the type of this resource
	 * 
	 * @return The type
	 */
	public ResourceType getType() {
		return type;
	}

	/**
	 * Sets the type of this resource
	 * 
	 * @param type
	 *            The type
	 */
	public void setType(ResourceType type) {
		this.type = type;
	}

	/**
	 * Returns the company this resource belongs to
	 * 
	 * @return The company or null if this is a common or global resource
	 */
	public Company getCompany() {
		return company;
	}

	/**
	 * Sets the company this resource belongs to
	 * 
	 * @param company
	 *            The company
	 */
	public void setCompany(Company company) {
		if (project == null) {
			this.company = company;
		}
	}

	/**
	 * Returns the project this resource belongs to
	 * 
	 * @return The project or null if this is a common or global resource
	 */
	public Project getProject() {
		return project;
	}

	/**
	 * Sets the project this resource belongs to
	 * 
	 * @param project
	 *            The project
	 */
	public void setProject(Project project) {
		if (project != null) {
			this.company = project.getCompany();
		}
		this.project = project;
	}

	/**
	 * Return the resource's name
	 * 
	 * @return The name
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the resource's name
	 * 
	 * @param name
	 *            The name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Returns the description of this resource explaining what it is for and which scenario
	 * 
	 * @return
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * Sets the description of this resource explaining what it is for and which scenario
	 * 
	 * @param description
	 */
	public void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Returns path to the resource object
	 * 
	 * @return The resource path
	 */
	public String getResourcePath() {
		return resourcePath;
	}

	/**
	 * Sets the path to the resource object
	 * 
	 * @param resourcePath
	 *            The resource path
	 */
	public void setResourcePath(String resourcePath) {
		this.resourcePath = resourcePath;
	}

	/**
	 * Returns the parent resource of this resource.
	 * 
	 * @return The parent resource
	 */
	public Resource getParentResource() {
		return parentResource;
	}

	/**
	 * Sets the parent resource of this resource.
	 * 
	 * @param resource
	 *            The parent resource
	 * @throws {@link UnsupportedOperationException} if the resource is itself
	 */
	public void setParentResource(Resource resource) {
		if (resource == this) {
			throw new UnsupportedOperationException("This resource cannot be set as its own parent");
		}
		this.parentResource = resource;
	}

	/**
	 * Returns the child resources of this resource.
	 * 
	 * IMPORTANT! If you use the list returned from this method to manipulate it, make sure you handle the parent-child relationship manually.
	 * 
	 * @return A list of child resources
	 */
	public List<Resource> getResources() {
		return resources;
	}

	/**
	 * Sets the child resources of this resource.
	 * 
	 * @param resource
	 *            The child resource to add
	 */
	public void addResource(Resource resource) {
		this.resources.add(resource);
		resource.parentResource = this;
	}

	/**
	 * Removes a child resource from this resource.
	 * 
	 * @param resource
	 *            The child resource to remove
	 */
	public void removeResource(Resource resource) {
		this.resources.remove(resource);
		resource.parentResource = null;
	}

	/**
	 * Returns the creation date of this resource
	 * 
	 * @return The creation date
	 */
	public Date getCreatedDate() {
		return created;
	}

	/**
	 * Sets the creation date of this resource
	 * 
	 * @param created
	 *            The creation date
	 */
	public void setCreatedDate(Date created) {
		this.created = created;
	}

	/**
	 * Returns the date this resource was last modified
	 * 
	 * @return The last modified date
	 */
	public Date getLastModifiedDate() {
		return lastModified;
	}

	/**
	 * Sets the date this resource was last modified
	 * 
	 * @param lastModified
	 *            The last modified date
	 */
	public void setLastModifiedDate(Date lastModified) {
		this.lastModified = lastModified;
	}

	/**
	 * Finds all resources of a specific type for a company.
	 * 
	 * @param company
	 *            The company
	 * @param resourceType
	 *            The resource type
	 * @return A list of resources
	 */
	public static List<Resource> findResourcesByCompany(Company company, ResourceType resourceType) {
		return find.where().eq("company.id", company.getId()).eq("type", resourceType).findList();
	}

	/**
	 * Finds all resources of a specific type for a project.
	 * 
	 * @param project
	 *            The project
	 * @param resourceType
	 *            The resource type
	 * @return A list of resources
	 */
	public static List<Resource> findResourcesByProject(Project project, ResourceType resourceType) {
		return find.where().eq("project.id", project.getId()).eq("type", resourceType).findList();
	}

	/**
	 * Finds a resource with a name of a specific type for a project.
	 * 
	 * @param project
	 *            The project
	 * @param type
	 *            The resource type
	 * @param name
	 *            The resource name
	 * @return The resource or null if it doesn't exist
	 */
	public static Resource findResourceByProject(Project project, ResourceType type, String name) {
		return find.where().eq("project.id", project.getId()).eq("type", type).eq("name", name).findUnique();
	}

	/**
	 * Get string shows if this resource for which scope
	 * 
	 * @return scope
	 *         <p>
	 *         Global
	 *         </p>
	 *         <p>
	 *         Company name
	 *         </p>
	 *         <p>
	 *         Project name
	 *         </p>
	 */
	public String getScope() {
		String scope = ConsoleConstants.SCOPE_GLOBAL;
		if (getCompany() != null)
			scope = getCompany().getCompanyId();
		if (getProject() != null)
			scope += ConsoleConstants.SCOPE_SEPARATOR + getProject().getName();
		return scope;
	}

	@Override
	public String toString() {
		return "Resource [id=" + id + ", type=" + type + ", company=" + company + ", project=" + project + ", name=" + name + ", description=" + description + ", resourcePath=" + resourcePath + ", created=" + created + ", lastModified=" + lastModified + "]";
	}

}
